package net.mayee.alice.web.controller.admin.system;

import jodd.log.Logger;
import jodd.log.LoggerFactory;
import jodd.props.Props;
import net.mayee.alice.common.Definiens;
import net.mayee.alice.filter.ShiroAdminDbRealm;
import net.mayee.alice.web.controller.base.BaseController;
import net.mayee.common.AliceHelper;
import net.mayee.common.utils.Agent;
import net.mayee.leo.LeoEncrypt;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Calendar;

@Controller
public class LoginController extends BaseController {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

    @RequestMapping(value = "/login")
    public String login(HttpServletRequest request, ModelMap model) throws IOException {
        Props props = AliceHelper.getInstance().getJoddProps();

        String userAgent = request.getHeader("USER-AGENT").toLowerCase();
        if (!Agent.isMobile(userAgent)) {
            if (Agent.isExclueBrowser(userAgent, Arrays.asList(props.getValue("system.browser.filter.notsupport").split(",")))) {
                model.addAttribute(Definiens.ERR_BROWSER,
                        this.getMessage("net.mayee.alice.login.msg.err_browser", "不支持当前浏览器类型,建议使用谷歌浏览器访问"));
            }
        } else {
            model.addAttribute(Definiens.ERR_MOBILE,
                    this.getMessage("net.mayee.alice.login.msg.err_mobile", "不支持移动端,请使用电脑登录"));
        }

        setDefaultSessionLocale();

        model.addAttribute("rsa_modulus", props.getValue("encrypt.rsa.modulus"));
        model.addAttribute("rsa_exponent", props.getValue("encrypt.rsa.public.exponent"));
        return "login";
    }

    @RequestMapping(value = "/doLogin", method = RequestMethod.GET)
    public String myLogin(ModelMap model)
            throws IOException {
        return "forward:/login";
    }

    @RequestMapping(value = "/doLogin", method = RequestMethod.POST)
    public String myLogin(@RequestParam("username") String username,
                          @RequestParam("password") String password,
                          @RequestParam("vi_code") String vi_code, ModelMap model)
            throws IOException {

        model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, username);

        String failMsg = null;
        Props props = AliceHelper.getInstance().getJoddProps();

        /* login action time check */
        if (isOverLoginTime()) {
            String time = props.getValue("system.login.time.second");
            model.addAttribute(
                    FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME,
                    this.getMessage("net.mayee.alice.login.msg.min_login_time", "登陆最小间隔时间" + time + "秒"));
            return "forward:/login";
        }

        /* validateimg check */
        if (isFailValidateimg(vi_code)) {
            model.addAttribute(
                    FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME,
                    this.getMessage("net.mayee.alice.login.msg.validateimg_error", "验证码错误！"));
            return "forward:/login";
        }

        /* decrypt password */
        String privateExponent = props.getValue("encrypt.rsa.private.exponent");
        String modulus = props.getValue("encrypt.rsa.modulus");

        byte[] de_password = null;
        try {
            de_password = LeoEncrypt.easyDecryptRSAByProviderPirvateKey(
                    modulus,
                    privateExponent,
                    new org.bouncycastle.jce.provider.BouncyCastleProvider(),
                    new BigInteger(password, 16).toByteArray()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }

        UsernamePasswordToken token = new UsernamePasswordToken(username,
                StringUtils.reverse(new String(de_password)));
        Subject currentUser = SecurityUtils.getSubject();
        try {
            /* check username and password */
            currentUser.login(token);

            /* set SessionLocaleResolver */
            ShiroAdminDbRealm.ShiroUser shiroUser = (ShiroAdminDbRealm.ShiroUser) currentUser.getPrincipal();
            this.setSessionLocale(shiroUser.getLagCode());
        } catch (UnknownAccountException uae) {//用户不存在
            failMsg = this.getMessage("net.mayee.alice.login.msg.wrong_username_or_password", "用户名或密码错误！");
        } catch (IncorrectCredentialsException ice) {
            failMsg = this.getMessage("net.mayee.alice.login.msg.wrong_username_or_password", "用户名或密码错误！");
        } catch (DisabledAccountException dae) {
            failMsg = this.getMessage("net.mayee.alice.login.msg.account_locked", "此账户已被锁定！");
        } catch (ExcessiveAttemptsException eae) {
            failMsg = this.getMessage("net.mayee.alice.login.msg.fail_many", "登录失败次数过多！");
        } catch (AuthenticationException ae) {
            failMsg = this.getMessage("net.mayee.alice.login.msg.login_error", "登陆异常！");
        }

        if (failMsg != null) {
            model.addAttribute(
                    FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME, failMsg);
            return "forward:/login";
        }

        //redirect
        return "redirect:/sdm/main";
    }

    @RequiresUser
    @RequestMapping(value = "/sdm/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:login";
    }

    private boolean isFailValidateimg(String vi_code) throws IOException {
        Props props = AliceHelper.getInstance().getJoddProps();
        if ("true".equals(props.getValue("validateimg.check"))) {
            HttpSession session = this.getRequest().getSession();
            if (session.getAttribute(Definiens.SESSIONKEY_LOGIN_VALIDATEIMG) != null) {
                // fail
                if (StringUtils.isBlank(vi_code)
                        || vi_code.trim().length() != 5
                        || !AliceHelper.getInstance().checkValidateImg(vi_code,
                        this.getRequest())) {
                    AliceHelper.getInstance().buildOrResetValidateImg(this.getRequest());
                    return true;
                }// success
                else {
                    AliceHelper.getInstance().removeValidateImg(this.getRequest());
                }
            }
        }
        return false;
    }

    private boolean isOverLoginTime() {
        Props props = AliceHelper.getInstance().getJoddProps();
        if ("true".equals(props.getValue("system.login.time.check"))) {
            HttpSession session = this.getRequest().getSession();
            if (session.getAttribute(Definiens.SESSIONKEY_LOGIN_LAST_TIME) != null) {
                long login_action_min_time = Definiens.DEF_LOGIN_ACTION_MIN_TIME;
                try {
                    login_action_min_time = Long.parseLong(props.getValue("system.login.time.min.time"));
                } catch (Exception e) {
                    LOGGER.error(
                            AliceHelper.getInstance().getApplicationConfigErrorMessage("system.login.time.min.time", login_action_min_time), e);
                }
                long last_time = (Long) session
                        .getAttribute(Definiens.SESSIONKEY_LOGIN_LAST_TIME);
                if (Calendar.getInstance().getTimeInMillis() - last_time < login_action_min_time) {
                    return true;
                }
            } else {
                /* first login, add login time */
                session.setAttribute(Definiens.SESSIONKEY_LOGIN_LAST_TIME, Calendar
                        .getInstance().getTimeInMillis());
            }
        }
        return false;
    }

}
